package com.lagou.factory;

import com.lagou.annotations.*;
import com.lagou.pojo.BeanDefinition;
import com.lagou.utils.FileUtils;
import com.lagou.utils.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 提供了IoC容器的实现
 * 这个类会扫描指定路径下的class文件
 * 然后根据注解实例化对象，并保存在单例池中提供使用
 */
public class BeanFactory {
    // 单例池
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    // 从文件夹里扫描出来的class信息
    private Map<String, String> classes = new ConcurrentHashMap<>();
    // 实例化对象之前的预处理信息
    private List<BeanDefinition> beanDefinitions = new CopyOnWriteArrayList<>();

    /**
     * 传入包名，文件夹路径，需要排除的文件夹，然后初始化IoC容器
     *
     * @param scanPkgName 包名
     * @param scanPkgPath 文件夹路径
     * @param excludedDir 排除的文件夹
     * @throws Exception
     */
    public BeanFactory(String scanPkgName, String scanPkgPath, String[] excludedDir) throws Exception {
        initBeanDefinitions(scanPkgName, scanPkgPath, excludedDir);
        initSingletonObjects();
    }

    /**
     * 扫描指定路径下的包，将符合要求的类的信息封装成BeanDefinition
     */
    private void initBeanDefinitions(String scanPkgName, String scanPkgPath, String[] excludedDir) throws ClassNotFoundException {
        // 进行扫描
        FileUtils fileUtils = new FileUtils();
        classes = fileUtils.scanClassesByPkgNameAndPkgPath(scanPkgName, scanPkgPath, Arrays.asList(excludedDir), classes);

        for (Map.Entry<String, String> entry : classes.entrySet()) {
            BeanDefinition beanDefinition = new BeanDefinition();
            Class<?> clazz = Class.forName(entry.getValue());
            // 如果是有这三种注解的类才需要用容器来管理
            if (clazz.isAnnotationPresent(Component.class) || clazz.isAnnotationPresent(Service.class) || clazz.isAnnotationPresent(Repository.class)) {
                // 设置bean的名字
                if (clazz.isAnnotationPresent(Component.class)) {
                    Component component = clazz.getAnnotation(Component.class);
                    beanDefinition.setName(component.value().isEmpty() ? entry.getKey() : component.value());
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service component = clazz.getAnnotation(Service.class);
                    beanDefinition.setName(component.value().isEmpty() ? entry.getKey() : component.value());
                } else {
                    Repository component = clazz.getAnnotation(Repository.class);
                    beanDefinition.setName(component.value().isEmpty() ? entry.getKey() : component.value());
                }
                // 设置bean的全限定类名
                beanDefinition.setReference(clazz.getName());
                // 判断是否设置了事务控制
                if (clazz.isAnnotationPresent(Transactional.class)) {
                    beanDefinition.setTransactionControl(true);
                }
                // 获取实现的接口列表
                Set<String> interfaces = new HashSet<>();
                for (Class<?> anInterface : clazz.getInterfaces()) {
                    interfaces.add(anInterface.getName());
                }
                beanDefinition.setImplementedInterfaces(interfaces);

                // 设置是否是接口
                beanDefinition.setInterface(interfaces.size() > 0);

                // 设置代理实现类的名字
                beanDefinition.setAopProxyName("transactionProxyFactory");

                // 最后加入到beanDefinitions里
                beanDefinitions.add(beanDefinition);
            }
        }
    }

    /**
     * 初始化所有的单例对象
     *
     * @throws Exception
     */
    private void initSingletonObjects() throws Exception {
        // 遍历预处理的信息
        for (BeanDefinition beanDefinition : beanDefinitions) {
            getBean(beanDefinition);
        }
    }

    /**
     * 根据bean的名称获取对应的BeanDefinition
     *
     * @param name bean name
     * @return 对应的BeanDefinition
     */
    private BeanDefinition getBeanDefinitionByBeanName(String name) {
        BeanDefinition beanDefinition = null;
        for (BeanDefinition definition : beanDefinitions) {
            if (definition.getName().equals(name)) {
                beanDefinition = definition;
                break;
            }
        }
        return beanDefinition;
    }

    /**
     * 暴露给外界使用的根据bean name获取实例的方法
     *
     * @param name bean name
     * @return 实例
     * @throws Exception
     */
    public Object getBean(String name) throws Exception {
        // 如果有就返回
        if (singletonObjects.containsKey(name)) {
            return singletonObjects.get(name);
        }

        // 没有就创建
        return createBean(getBeanDefinitionByBeanName(name));
    }

    /**
     * 一个多态方法，根据BeanDefinition获取对象实例
     *
     * @param beanDefinition 类的预处理信息
     * @return bean
     * @throws Exception
     */
    private Object getBean(BeanDefinition beanDefinition) throws Exception {
        // 有就返回
        if (singletonObjects.containsKey(beanDefinition.getName())) {
            return getBean(beanDefinition.getName());
        }

        // 没有就创建
        return createBean(beanDefinition);
    }

    /**
     * 这个方法用来解析字段，面主要做三件事
     * 1、 解析类注解，并实例化，放入singletonObjects里
     * 2、 解析属性，进行依赖注入
     * 3、 解析方法，结合类注解中的事务控制注解来创建动态代理
     *
     * @param beanDefinition 类的预处理数据
     * @return
     * @throws Exception
     */
    private Object createBean(BeanDefinition beanDefinition) throws Exception {
        // 根据全限定类名获取到类
        Class<?> clazz = Class.forName(beanDefinition.getReference());

        // 实例化bean
        Object object = clazz.newInstance();

        // 解析字段
        parserAutoWired(object);

        // 解析并获取到需要做事务控制的方法列表
        List<String> transactionalMethods = parserTransactional(clazz);

        // 如果方法列表不为空的话，创建动态代理
        object = createTransactionProxyIfNecessary(object, beanDefinition, transactionalMethods);

        // 完成了bean的创建，将创建好的bean放入单例池中
        singletonObjects.put(beanDefinition.getName(), object);

        // 将实例返回
        return object;
    }

    /**
     * 处理AutoWired注解
     *
     * @param object 创建好的对象实例
     * @throws Exception
     */
    private void parserAutoWired(Object object) throws Exception {
        // 反射获取到全部的字段
        Field[] fields = object.getClass().getDeclaredFields();

        // 进行遍历
        for (Field field : fields) {
            // 如果这个字段设置了自动注入依赖才要处理
            if (field.isAnnotationPresent(AutoWired.class)) {
                AutoWired autoWired = field.getAnnotation(AutoWired.class);
                // 获取到注解中的参数的值
                String targetName = autoWired.value();
                // 如果是接口，那就去找实现了这个接口的实例
                if (field.getType().isInterface()) {
                    // 如果没有指定名字，直接找这个接口的实现，如果超过一个，就抛异常
                    if (targetName.isEmpty()) {
                        // 首先在beanDefinitions里找实现了这个接口的beanDefinition
                        int count = 0;

                        // 遍历全部的bean，看一下这个接口是否有多个实现
                        BeanDefinition targetBeanDefinition = null;
                        for (BeanDefinition b : beanDefinitions) {
                            if (b.getImplementedInterfaces().contains(field.getType().getName())) {
                                targetBeanDefinition = b;
                                count++;
                            }
                        }

                        // 如果有多个实现，或者没找到对应的beanDefinition，抛异常
                        if (count != 1) {
                            throw new IllegalStateException("no or more then one implements for interface: " + field.getType());
                        }

                        // 否则通过beanDefinition的name去singletonBeans里找，然后设置到实例的字段里
                        setFieldReference(field, object, getBean(targetBeanDefinition));
                    }
                    // 否则如果指定了name，那就用这个名字去找
                    else {
                        setFieldReference(field, object, getBean(getBeanDefinitionByBeanName(targetName)));
                    }
                }
                // 否则是实体类，那就去找这个实体类的实例
                else {
                    // 如果没有指定名字，那就去找类的实例
                    if (targetName.isEmpty()) {
                        for (BeanDefinition b : beanDefinitions) {
                            if (b.getReference().equals(field.getType().getName())) {
                                setFieldReference(field, object, getBean(b));
                                break;
                            }
                        }
                    }
                    // 否则如果指定了name，那就用这个名字去找
                    else {
                        setFieldReference(field, object, getBean(getBeanDefinitionByBeanName(targetName)));
                    }
                }
            }
        }
    }

    /**
     * 通过反射设置实例的字段的引用
     *
     * @param field        字段
     * @param object       实例
     * @param targetObject 目标引用
     * @throws Exception
     */
    private void setFieldReference(Field field, Object object, Object targetObject) throws Exception {
        // 如果是public字段，直接设置
        if (field.isAccessible()) {
            field.set(object, targetObject);
        }
        // 否则实行暴力操作
        else {
            field.setAccessible(true);
            field.set(object, targetObject);
            field.setAccessible(false);
        }
    }

    /**
     * 这个方法用来解析声明式事务的注解
     * 首先要判断这个类有没有事务控制的注解
     * 如果有的话，对指定的方法名前缀进行过滤，过滤出需要进行事务控制的方法，并且如果对其他方法也注解了声明式事务的话，也需要过滤出来
     * 如果没有的话，再看每一个方法有没有配置声明式事务
     * 最后的结果是需要进行事务控制的方法索引集合
     *
     * @param clazz 类
     * @return 需要进行事务控制的方法索引集合
     */
    private List<String> parserTransactional(Class<?> clazz) {
        // 用来保存需要增加事务控制的方法索引
        List<String> transactionalMethods = new ArrayList<>();
        // 拿到类中定义的方法
        Method[] methods = clazz.getDeclaredMethods();

        // 判断这个类是否有声明式事务注解
        if (clazz.isAnnotationPresent(Transactional.class)) {
            // 获取Transactional注解
            Transactional transactional = clazz.getAnnotation(Transactional.class);

            // 拿到注解配置的前缀名，默认是{"insert", "delete", "update", "select"}
            String[] methodPrefixes = transactional.methodPrefix();

            for (Method method : methods) {
                for (String methodPrefix : methodPrefixes) {
                    // 判断前缀 + 判断这个方法有没有声明式事务注解
                    if (method.getName().startsWith(methodPrefix) || method.isAnnotationPresent(Transactional.class)) {
                        // 获取方法索引：方法名+返回值类型+参数类型
                        // 加入到需要管理事务的方法集合中
                        transactionalMethods.add(StringUtils.getMethodIndexByNameAndReturnTypeAndParameterTypes(method));
                    }
                }
            }
        }
        // 如果这个类的声明中没有事务注解的话，就遍历这个类的所有方法
        else {
            for (Method method : methods) {
                // 如果方法有声明式事务的注解的话
                if (method.isAnnotationPresent(Transactional.class)) {
                    // 拼接方法索引：方法名+返回值类型+参数类型
                    // 加入到需要管理事务的方法集合中
                    transactionalMethods.add(StringUtils.getMethodIndexByNameAndReturnTypeAndParameterTypes(method));
                }
            }
        }
        return transactionalMethods;
    }

    /**
     * 如果需要进行事务控制的方法索引集合不为空的话，创建代理对象
     *
     * @param object               实例
     * @param beanDefinition       类的预定义信息
     * @param transactionalMethods 需要进行事务控制的方法索引集合
     * @return
     * @throws Exception
     */
    private Object createTransactionProxyIfNecessary(Object object, BeanDefinition beanDefinition, List<String> transactionalMethods) throws Exception {
        // 如果长度大于0，才需要创建代理对象
        if (transactionalMethods.size() > 0) {
            // 获取代理工厂
            TransactionProxyFactory transactionProxyFactory = (TransactionProxyFactory) getBean(getBeanDefinitionByBeanName(beanDefinition.getAopProxyName()));
            // 创建代理对象
            object = transactionProxyFactory.getProxy(object, transactionalMethods, beanDefinition.isInterface());
        }
        return object;
    }
}
